﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Header
    {
        public readonly List<Todo> Todos = new List<Todo>();
        private string text1;
        private string text2;
        private string text3;

        public object CountHtml { get; set; }

        public object ActiveCountHtml { get; set; }

        public object ComplitedCountHtml { get; set; }

        public string Text1
        {
            get { return text1; }
            set
            {
                Console.WriteLine("Text1");
                text1 = value;
            }
        }

        public string Text2
        {
            get { return text2; }
            set
            {
                Console.WriteLine("Text2");
                text2 = value;
            }
        }

        public string Text3
        {
            get { return text3; }
            set
            {
                Console.WriteLine("Text3");
                text3 = value;
            }
        }
    }

    class Todo
    {
        public bool Complited { get; set; }

        public object Tag { get; set; }
    }

    class Program
    {
        private static void Main()
        {
            var cells = new Cells();

            var header = new Header();
            var todos = cells.Cell(() => header.Todos);
            var count = cells.Cell(() => todos.Value.Count);
            var activeCount = cells.Cell(() => todos.Value.Count(_ => !_.Tag().Complited.Value));
            cells.Cell(() => header.CountHtml = count.Value);
            cells.Cell(() => header.ActiveCountHtml = activeCount.Value);
            cells.Cell(() => header.ComplitedCountHtml = count.Value - activeCount.Value);
            var text1 = cells.Cell(() => header.Text1);
            cells.Cell(() => header.Text2 = text1.Value);
            cells.Cell(() => header.Text3 = text1.Value);
            var text2 = cells.Cell(() => header.Text2);
            cells.Cell(() => header.Text1 = text2.Value);

            AddTodo(false, cells, header, todos);
            cells.Calc();
            Assert(header.CountHtml.Equals(1));
            Assert(header.ActiveCountHtml.Equals(1));
            Assert(header.ComplitedCountHtml.Equals(0));
            AddTodo(false, cells, header, todos);
            cells.Calc();
            Assert(header.CountHtml.Equals(2));
            Assert(header.ActiveCountHtml.Equals(2));
            Assert(header.ComplitedCountHtml.Equals(0));

            {
                var todo1 = header.Todos[1];
                todo1.Complited = true;
                todo1.Tag().Complited.Reset();
                cells.Calc();
                Assert(header.CountHtml.Equals(2));
                Assert(header.ActiveCountHtml.Equals(1));
                Assert(header.ComplitedCountHtml.Equals(1));
            }

            {
                var todo1 = header.Todos[0];
                todo1.Complited = true;
                todo1.Tag().Complited.Reset();
                cells.Calc();
                Assert(header.CountHtml.Equals(2));
                Assert(header.ActiveCountHtml.Equals(0));
                Assert(header.ComplitedCountHtml.Equals(2));
            }

            {
                cells.Calc();
                header.Text2 = "test1";
                Console.WriteLine("**********");
                text2.Reset();
                cells.Calc();
                Assert(header.Text2.Equals("test1"));
            }
        }

        private static void AddTodo(bool complited, Cells cells, Header header, ICell<List<Todo>> todosCell)
        {
            var item = new Todo{Complited = complited};
            item.Tag(new TodoTag(
                complited: cells.Cell(() => item.Complited)));
            header.Todos.Add(item);
            todosCell.Reset();
        }

// ReSharper disable UnusedParameter.Local
        private static void Assert(bool b)
// ReSharper restore UnusedParameter.Local
        {
            if (!b) throw new ApplicationException();
        }
    }

    class TodoTag
    {
        public readonly ICell<bool> Complited;

        public TodoTag(ICell<bool> complited)
        {
            Complited = complited;
        }
    }

    public class Cells
    {
        private readonly List<ICell> list = new List<ICell>();
        private readonly Stack<List<ICell>> stack = new Stack<List<ICell>>();
        public ICell<TResult> Cell<TResult>(Func<TResult> func)
        {
            var result = new CellImpl<TResult>(func, this);
            list.Add(result);
            return result;
        }

        public class CellImpl<T> : ICell<T>
        {
            private readonly Func<T> func;
            private readonly Cells parent;
            private readonly HashSet<ICell> dependentCells = new HashSet<ICell>();
            private T value;
            private bool isCalculated;

            public CellImpl(Func<T> func, Cells parent)
            {
                this.func = func;
                this.parent = parent;
            }

            public T Value
            {
                get
                {
                    Calc();
                    return value;
                }
            }

            public void Calc()
            {
                if (parent.stack.Count > 0)
                    parent.stack.Peek().Add(this);
                if (!isCalculated)
                {
                    parent.stack.Push(new List<ICell>());
                    value = func();
                    foreach (var cell in parent.stack.Pop())
                        cell.AddDependentCell(this);
                    isCalculated = true;
                }
            }

            public void AddDependentCell(ICell cell)
            {
                dependentCells.Add(cell);
            }

            public void Reset()
            {
                foreach (var dependentCell in dependentCells)
                    dependentCell.Reset();
                isCalculated = false;
            }
        }

        public void Calc()
        {
            foreach (var cell in list)
                cell.Calc();
        }
    }

    public interface ICell
    {
        void Reset();
        void Calc();
        void AddDependentCell(ICell cell);
    }

    public interface ICell<out T>: ICell
    {
        T Value { get; }
    }

    static class TodoEx
    {
        public static void Tag(this Todo it, TodoTag value)
        {
            it.Tag = value;
        }

        public static TodoTag Tag(this Todo it)
        {
            return (TodoTag)it.Tag;
        }        
    }

}
